import os
import zipfile
import tempfile
import shutil  # 补充缺失的导入
from datetime import datetime

from file_utils import normalize_jar_path, jar_path_to_system_path
import sys

## JAR 包处理类

class JarProcessor:
    def __init__(self):
        self.extracted_libs = {}  # 存储已解压的lib包路径
        self.lib_file_timestamps = {}  # 存储lib包内文件的时间戳
        self.manifest_content = None  # MANIFEST.MF内容
        self.lib_file_permissions = {}  # 存储文件权限
        self.lib_file_compression = {}  # 新增：存储文件压缩方式
        self.target_file_permission = 0o644  # 对应-rw-r--r--的权限值

    def get_jar_entries(self, jar_path, temp_dir, recursive=True):
        """获取JAR包中所有条目的列表（使用正斜杠路径），支持递归解析内部JAR"""
        entries = []
        with zipfile.ZipFile(jar_path, 'r') as jar_file:
            for entry in jar_file.namelist():
                normalized = normalize_jar_path(entry)
                entries.append(normalized)
                
                # 递归解析lib目录下的JAR
                if recursive and normalized.startswith("BOOT-INF/lib/") and normalized.endswith(".jar"):
                    with tempfile.NamedTemporaryFile(delete=False, suffix=".jar", dir=temp_dir) as temp_lib:
                        temp_lib.write(jar_file.read(entry))
                        temp_lib_path = temp_lib.name
                    
                    inner_entries = self.get_jar_entries(temp_lib_path, temp_dir, recursive)
                    for inner_entry in inner_entries:
                        entries.append(f"{normalized}/{inner_entry}")
                    
                    os.unlink(temp_lib_path)
        
        return entries

    def extract_manifest(self, jar_path, jar_temp_dir, log_callback):
        """提取并保存MANIFEST.MF文件内容"""
        manifest_path = "META-INF/MANIFEST.MF"
        try:
            with zipfile.ZipFile(jar_path, 'r') as jar_file:
                if manifest_path in jar_file.namelist():
                    with jar_file.open(manifest_path) as manifest_file:
                        self.manifest_content = manifest_file.read()
                    
                    jar_file.extract(manifest_path, jar_temp_dir)
                    log_callback("已提取MANIFEST.MF文件")
                else:
                    log_callback("JAR包中未找到MANIFEST.MF文件")
        except Exception as e:
            log_callback(f"提取MANIFEST.MF文件时出错: {str(e)}")

    def extract_lib_package(self, lib_name, jar_temp_dir, temp_dir, log_callback):
        """从主JAR包中解压指定的lib包到临时目录，并记录时间戳和压缩方式"""
        if lib_name in self.extracted_libs:
            return self.extracted_libs[lib_name]
            
        lib_jar_path = os.path.join("BOOT-INF", "lib", lib_name)
        main_jar_path = os.path.join(jar_temp_dir, lib_jar_path.replace("/", os.sep))
        
        try:
            # 修改临时目录路径，确保它位于主JAR解压目录之外
            lib_temp_dir = os.path.join(temp_dir, "extracted_libs", lib_name)
            os.makedirs(lib_temp_dir, exist_ok=True)
            self.lib_file_timestamps[lib_name] = {}
            self.lib_file_compression[lib_name] = {}  # 初始化压缩方式字典
            self.lib_file_permissions[lib_name] = {}  # 添加此行初始化权限字典
            
            if os.path.exists(main_jar_path):
                with zipfile.ZipFile(main_jar_path, 'r') as lib_jar:
                    total_entries = len([f for f in lib_jar.infolist() if not f.is_dir()])
                    
                    # 记录时间戳、权限和压缩方式
                    for info in lib_jar.infolist():
                        if info.is_dir():
                            continue
                        normalized_path = normalize_jar_path(info.filename)
                        dt = datetime(*info.date_time)
                        self.lib_file_timestamps[lib_name][normalized_path] = dt.timestamp()
                        self.lib_file_permissions[lib_name][normalized_path] = info.external_attr
                        self.lib_file_compression[lib_name][normalized_path] = info.compress_type  # 记录压缩方式
                    
                    lib_jar.extractall(lib_temp_dir)
                    # 校验解压后的文件数量
                    extracted_files = sum([len(files) for _, _, files in os.walk(lib_temp_dir)])
                    if extracted_files != total_entries:
                        log_callback(f"警告: {lib_name} 解压不完整，原始{total_entries}个文件，实际解压{extracted_files}个")
                
                log_callback(f"已解压lib包: {lib_name} 到临时目录，共记录 {len(self.lib_file_timestamps[lib_name])} 个文件的时间戳和压缩方式")
                self.extracted_libs[lib_name] = lib_temp_dir
                return lib_temp_dir
            else:
                log_callback(f"主JAR中未找到lib包: {lib_name}")
                raise Exception(f"主JAR中未找到lib包: {lib_name}")
                # os.makedirs(lib_temp_dir, exist_ok=True)
                # self.extracted_libs[lib_name] = lib_temp_dir
                # return lib_temp_dir
                
        except Exception as e:
            error_msg = f"解压lib包 {lib_name} 时出错: {str(e)}"
            log_callback(error_msg)
            raise Exception(error_msg)

    def repackage_lib(self, lib_name, lib_temp_dir, jar_temp_dir, log_callback):
        """将修改后的lib包重新打包，保持原始文件时间戳、压缩方式和目录结构"""
        if lib_name not in self.extracted_libs:
            log_callback(f"未找到已解压的lib包: {lib_name}，无需重新打包")
            return
            
        lib_temp_dir = self.extracted_libs[lib_name]
        lib_jar_path = os.path.join(jar_temp_dir, "BOOT-INF", "lib", lib_name)
        
        try:
            # 确保lib包目录存在
            os.makedirs(os.path.dirname(lib_jar_path), exist_ok=True)
            
            # 检查是否与主JAR包同名，如果是则添加特殊后缀
            if lib_name == os.path.basename(jar_temp_dir.rstrip('/')) + '.jar':
                backup_path = lib_jar_path + '.backup'
                if os.path.exists(lib_jar_path):
                    shutil.copy2(lib_jar_path, backup_path)
                    log_callback(f"已备份同名lib包: {lib_name} 到 {backup_path}")
            
            # 重新打包lib包，使用原始压缩方式
            # 设置最高压缩级别(compresslevel=9)并显式指定压缩方法
            # 兼容旧版本Python（compresslevel参数在Python 3.7+才支持）
            try:
                lib_jar = zipfile.ZipFile(lib_jar_path, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=9)
            except TypeError:
                # Python 3.6及以下版本不支持compresslevel参数
                lib_jar = zipfile.ZipFile(lib_jar_path, 'w', compression=zipfile.ZIP_DEFLATED)
            
            with lib_jar:
                # 遍历解压后的lib包内容
                for root, dirs, files in os.walk(lib_temp_dir):
                    # 处理目录
                    for dir_name in dirs:
                        dir_path = os.path.join(root, dir_name)
                        relative_path = os.path.relpath(dir_path, lib_temp_dir)
                        normalized_path = normalize_jar_path(relative_path) + '/'
                        
                        # 创建目录的ZipInfo
                        zip_info = zipfile.ZipInfo(normalized_path)
                        zip_info.external_attr = 0o40755 << 16  # 目录权限
                        zip_info.compress_type = zipfile.ZIP_STORED
                        
                        # 设置时间戳
                        if lib_name in self.lib_file_timestamps and normalized_path.rstrip('/') in self.lib_file_timestamps[lib_name]:
                            ts = self.lib_file_timestamps[lib_name][normalized_path.rstrip('/')]
                            dt = datetime.fromtimestamp(ts)
                            zip_info.date_time = (dt.year, dt.month, dt.day, 
                                                dt.hour, dt.minute, dt.second)
                        
                        lib_jar.writestr(zip_info, b'')
                    
                    # 处理文件
                    for file in files:
                        file_path = os.path.join(root, file)
                        relative_path = os.path.relpath(file_path, lib_temp_dir)
                        normalized_path = normalize_jar_path(relative_path)
                        
                        # 创建文件的ZipInfo
                        zip_info = zipfile.ZipInfo.from_file(file_path, normalized_path)
                        
                        # 使用记录的原始压缩方式，如果没有记录则使用默认方式
                        if lib_name in self.lib_file_compression and normalized_path in self.lib_file_compression[lib_name]:
                            zip_info.compress_type = self.lib_file_compression[lib_name][normalized_path]
                        elif normalized_path.endswith('.class'):
                            # 将.class文件默认压缩方式从ZIP_STORED改为ZIP_DEFLATED
                            zip_info.compress_type = zipfile.ZIP_DEFLATED
                        else:
                            zip_info.compress_type = zipfile.ZIP_DEFLATED
                        
                        # 保留原始文件权限
                        if lib_name in self.lib_file_permissions and normalized_path in self.lib_file_permissions[lib_name]:
                            zip_info.external_attr = self.lib_file_permissions[lib_name][normalized_path]
                        else:
                            file_stat = os.stat(file_path)
                            zip_info.external_attr = file_stat.st_mode << 16
                        
                        # 设置时间戳
                        if lib_name in self.lib_file_timestamps and normalized_path in self.lib_file_timestamps[lib_name]:
                            ts = self.lib_file_timestamps[lib_name][normalized_path]
                            dt = datetime.fromtimestamp(ts)
                            zip_info.date_time = (dt.year, dt.month, dt.day, 
                                                dt.hour, dt.minute, dt.second)
                        
                        with open(file_path, 'rb') as f:
                            lib_jar.writestr(zip_info, f.read())
            
            # 新增：设置重新打包后的lib文件的时间戳，确保与原始文件时间戳一致
            if os.path.exists(lib_jar_path) and lib_name in self.lib_file_timestamps:
                # 获取lib包中任意一个文件的时间戳作为lib包的时间戳
                for file_path in self.lib_file_timestamps[lib_name]:
                    ts = self.lib_file_timestamps[lib_name][file_path]
                    os.utime(lib_jar_path, (ts, ts))
                    break
            
            log_callback(f"已重新打包并替换lib包: {lib_name}，保持原始结构、权限和压缩方式")
            
            # 恢复备份的同名lib包
            backup_path = lib_jar_path + '.backup'
            if os.path.exists(backup_path):
                os.remove(backup_path)
                log_callback(f"已清理同名lib包备份文件: {backup_path}")
                
        except Exception as e:
            error_msg = f"重新打包lib包 {lib_name} 时出错: {str(e)}"
            log_callback(error_msg)
            raise Exception(error_msg)

    def process_lib_files(self, lib_name, temp_dir, log_callback):
        """处理lib组件内的文件替换/新增"""
        lib_extracted_dir = self.extracted_libs.get(lib_name)
        if not lib_extracted_dir or not os.path.exists(lib_extracted_dir):
            log_callback(f"警告: 未找到 {lib_name} 的解压目录")
            return []
        
        update_results = []
        source_lib_dir = os.path.join(temp_dir, lib_name)
        # 新增：验证替换文件目录并输出实际路径
        if not os.path.exists(source_lib_dir):
            error_msg = f"替换文件目录不存在: {source_lib_dir}"
            log_callback(f"错误: {error_msg}")
            update_results.append(error_msg)
            return update_results
        
        for root, _, files in os.walk(source_lib_dir):
            for file in files:
                source_file = os.path.join(root, file)
                relative_path = os.path.relpath(source_file, source_lib_dir)
                target_file = os.path.join(lib_extracted_dir, relative_path)
                
                os.makedirs(os.path.dirname(target_file), exist_ok=True)
                
                try:
                    # 新增：复制前检查源文件
                    if not os.path.exists(source_file):
                        update_results.append(f"源文件不存在: {source_file}")
                        continue
                    
                    shutil.copy2(source_file, target_file)
                    
                    # 增强：验证文件复制结果
                    if os.path.exists(target_file):
                        # 新增MD5校验确保文件内容已更新
                        if self._file_md5(source_file) == self._file_md5(target_file):
                            log_callback(f"已更新lib组件文件: {lib_name}/{relative_path}")
                            update_results.append(f"更新: {lib_name}/{relative_path}")
                        else:
                            error_msg = f"文件复制成功但内容不匹配: {lib_name}/{relative_path}"
                            update_results.append(error_msg)
                            log_callback(f"错误: {error_msg}")
                    else:
                        error_msg = f"文件复制失败: {lib_name}/{relative_path}"
                        update_results.append(error_msg)
                        log_callback(f"错误: {error_msg}")
                except Exception as e:
                    error_msg = f"处理lib文件时出错 {lib_name}/{relative_path}: {str(e)}"
                    update_results.append(error_msg)
                    log_callback(error_msg)
        
        return update_results

    import hashlib  # 移至文件顶部

    def _file_md5(self, file_path):
        """计算文件MD5值用于校验
        
        Args:
            file_path (str): 文件路径
            
        Returns:
            str: 文件的MD5十六进制摘要
            
        Raises:
            FileNotFoundError: 当文件不存在时
            PermissionError: 当无权限访问文件时
        """
        hash_md5 = hashlib.md5()
        try:
            with open(file_path, 'rb') as f:  # 修正引号转义问题
                # 分块读取大文件，避免占用过多内存
                for chunk in iter(lambda: f.read(4096), b""):
                    hash_md5.update(chunk)
            return hash_md5.hexdigest()
        except Exception as e:
            # 可根据需要细化异常处理逻辑
            raise e  # 或捕获后进行日志记录等操作

    def _set_file_permissions(self, file_path):
        """设置文件权限为-rw-r--r--"""
        try:
            # 获取当前文件权限
            current_stat = os.stat(file_path)
            
            # 保留原有权限中的特殊位，仅修改用户、组和其他用户的读写执行权限
            new_mode = (current_stat.st_mode & ~0o777) | self.target_file_permission
            os.chmod(file_path, new_mode)
            
            # 验证权限是否设置成功
            updated_stat = os.stat(file_path)
            if (updated_stat.st_mode & 0o777) == self.target_file_permission:
                return True
            return False
        except Exception as e:
            print(f"设置文件权限失败 {file_path}: {str(e)}")
            return False

    def __del__(self):
        """对象销毁时清理临时目录"""
        for lib_name, temp_dir in self.extracted_libs.items():
            if os.path.exists(temp_dir):
                shutil.rmtree(temp_dir, onerror=self.handle_remove_readonly)
        self.extracted_libs.clear()